/***************************************************************************
 *   CPPSocket - library                                                   *
 *                                                                         *
 *   Copyright (C) 2008 by kuerant                                         *
 *                         <kuerant@gmail.com>                             *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This library is distributed in the hope that it will be usefull, but  *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      *
 *   Library General Public License for more details.                      *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; if not, write to the Free Software   *
 *   Foundation, Inc., 59 Temple Place - Suite 330, Boston,                *
 *   MA 02111-1307, USA                                                    *
 ***************************************************************************/

#include "zsocket/microtime.hpp"
#include "zsocket/udper.hpp"

namespace zsocket
{
    Udper::Udper()
        : BUF_SIZE(65536)
    {
        buffer = new char[BUF_SIZE];
    }
    
    Udper::~Udper()
    {
        if (NULL != buffer) {
            delete [] buffer;
        }
    }
    
    void Udper::open(Address::port_type port) throw (Exception)
    {
        socket.open(Socket::UDP);

        setup();
        
        socket.bind(Address(Address::ANY_IP, port, false));
    }

    void Udper::close() throw (Exception)
    {
        socket.close();
    }

    void Udper::sendto(const Address& addr, const std::string& data, int iTimeout) throw (Exception)
    {
        socket.sendto(addr, data.c_str(), data.size(), 0);
    }

    void Udper::sendto(const Address& addr, const void* pvData, unsigned iDataSize, int timeout) throw (Exception)
    {
        const char* pszData = static_cast<const char*>(pvData);
        
        socket.sendto(addr, pszData, iDataSize, 0);
    }
    
    void Udper::recvfrom(Address& addr, std::string& data, int iTimeout) throw (Exception)
    {
        sockaddr_in _addr;
 
        bool    bTestTimeout = false;
        microtime_t    timeout;
        if (iTimeout >= 0) {
            timeout = microtime() + iTimeout;
            bTestTimeout = true;
        }
        
        while ( true ) {
            try {
                int iReturn = socket.recvfrom(_addr, buffer, BUF_SIZE, 0);
                if (iReturn > 0) {
                    data.assign(buffer, iReturn);
                    addr = _addr;

                    return;
                }
            }
            catch(Exception& ex) {
                if ( EAGAIN != ex.getCode() ) {
                    throw ex;
                }
            }
                            
            if ( bTestTimeout ) {
                microtime_t    now = microtime();
                if (now > timeout) {
                    char    _szMessage[64];
                    int     _iLength = snprintf(_szMessage, sizeof(_szMessage) - 1, "%d seconds timeout.", iTimeout );
                    _szMessage[_iLength] = '\0';
                    throw Exception(__func__, _szMessage, Socket::TIMEOUT_CODE);
                }
            }
        }   // while (true)
    }

    Address Udper::getAddress() throw (Exception)
    {
        return socket.getsockname();
    }
    
    void    Udper::setup(void) throw (Exception)
    {
        int iReturn;
        int iSocket = socket;
        
        for( int i=10; i<240; i+=10 ) {
            int iOption = 1024 * i;
            iReturn = setsockopt( iSocket, SOL_SOCKET, SO_SNDBUF, &iOption, sizeof(iOption) );
            if ( iReturn < 0 ) {
                break;
            }
        }
        for( int i=10; i<240; i+=10 ) {
            int iOption = 1024 * i;
            iReturn = setsockopt( iSocket, SOL_SOCKET, SO_RCVBUF, &iOption, sizeof(iOption) );
            if ( iReturn < 0 ) {
                break;
            }
        }

        struct timeval  timeout;
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;
        iReturn = setsockopt( iSocket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout) );
        if ( iReturn < 0 ) {
            throw   LibCException(__func__); 
        }
        timeout.tv_sec = 1;
        timeout.tv_usec = 500000;
        iReturn = setsockopt( iSocket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout) );
        if ( iReturn < 0 ) {
            throw   LibCException (__func__); 
        }
    }
    
    int     Udper::descriptor(void)
    {
        int     iFD = socket;
        
        return  iFD;
    }
    
}
